﻿#include "stdafx.h"
#include "qg/qgenv.h"
#include "qg/qgimage.h"
#include <setjmp.h>

#if _SB_WINDOWS_
#	include "jpeg/jpeglib.h"
#	include "jpeg/jerror.h"
#	ifdef _MSC_VER
#		if _SB_64_
#			if _SB_DEBUG_
#				pragma comment(lib, "jpeg64_debug.lib")
#			else
#				pragma comment(lib, "jpeg64.lib")
#			endif
#		else
#			if _SB_DEBUG_
#				pragma comment(lib, "jpeg_debug.lib")
#			else
#				pragma comment(lib, "jpeg.lib")
#			endif
#		endif
#	endif
#else
#	include <jpeglib.h>
#	include <jerror.h>
#endif

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4324)	// __declspec(align()) 때문에 구조체가 채워졌습니다.
#pragma warning(disable:4611)	// '_setjmp' 및 C++ 개체 소멸 사이의 상호 작용이 이식 가능하지 않습니다.
#endif

#define BUFSIZE	8192

namespace _img
{
	class ImageJpegLoader : public qgReader, public tpl::MemBase
	{
	public:
		struct ErrorHandler
		{
			jpeg_error_mgr	pub;
			jmp_buf			buf;
		};

	public:
		static void _InitSource(j_decompress_ptr /*ptr*/)
		{
		}

		static boolean _FillInputBuffer(j_decompress_ptr /*ptr*/)
		{
			return TRUE;
		}

		static void _SkipInputData(j_decompress_ptr ptr, long count)
		{
			if (count > 0)
			{
				struct jpeg_source_mgr* src = ptr->src;
				src->bytes_in_buffer -= count;
				src->next_input_byte += count;
			}
		}

		static void _TermSource(j_decompress_ptr /*ptr*/)
		{
		}

		static void _ErrorExit(j_common_ptr ptr)
		{
			(*ptr->err->output_message)(ptr);

			ErrorHandler* eh = (ErrorHandler*)ptr->err;

			longjmp(eh->buf, 1);
		}

		static void _OutputMessage(j_common_ptr ptr)
		{
			char tmp[JMSG_LENGTH_MAX];

			(*ptr->err->format_message)(ptr, tmp);

			k_trace(KLV_ERROR, "qg.jpeg", tmp);
		}

	public:
		//
		K_OVR void Dispose()
		{
			delete this;
		}

		//
		K_OVR bool Test(kconstpointer data, kint size)
		{
			kint jfif;

			memcpy(&jfif, (kbyte*)data + 6, sizeof(kint));

			return jfif == 0x4a464946 || jfif == 0x4649464a;
		}

		//
		K_OVR kbool Read(kObj* ctnr, kconstpointer data, kint size, kint flags)
		{
			qgImage* img = (qgImage*)ctnr;
			jpeg_decompress_struct jptr;
			jpeg_source_mgr jsrc;
			ErrorHandler jeh;
			kcham cmyk;
			kint rowspan, width, height, i, z, tmp;
			kbyte** rowptr = NULL;
			kbyte* output;
			kbyte* buf;

			//
			memset(&jptr, 0, sizeof(jpeg_decompress_struct));
			jptr.err = jpeg_std_error(&jeh.pub);
			jptr.err->error_exit = _ErrorExit;
			jptr.err->output_message = _OutputMessage;

			if (setjmp(jeh.buf))
			{
				jpeg_destroy_decompress(&jptr);
				k_delete(rowptr);

				return false;
			}

			//
			jpeg_create_decompress(&jptr);

			jptr.src = &jsrc;
			jsrc.bytes_in_buffer = size;
			jsrc.next_input_byte = (JOCTET*)data;
			jsrc.init_source = _InitSource;
			jsrc.fill_input_buffer = _FillInputBuffer;
			jsrc.skip_input_data = _SkipInputData;
			jsrc.resync_to_restart = jpeg_resync_to_restart;
			jsrc.term_source = _TermSource;

			jpeg_read_header(&jptr, TRUE);

			if (jptr.jpeg_color_space == JCS_CMYK)
			{
				jptr.out_color_space = JCS_CMYK;
				jptr.out_color_components = 4;
				cmyk = TRUE;
			}
			else
			{
				jptr.out_color_space = JCS_RGB;
				jptr.out_color_components = 3;
				cmyk = false;
			}

			jptr.output_gamma = 2.2;
			jptr.do_fancy_upsampling = false;

			//
			jpeg_start_decompress(&jptr);

			rowspan = jptr.image_width * jptr.out_color_components;
			width = jptr.image_width;
			height = jptr.image_height;
			output = k_new(rowspan * height, kbyte);

			//
			rowptr = k_new(height, kbyte*);

			for (i = 0; i < height; i++)
				rowptr[i] = &output[i * rowspan];

			tmp = 0;

			while (jptr.output_scanline < jptr.output_height)
				tmp += jpeg_read_scanlines(&jptr, &rowptr[tmp], jptr.output_height - tmp);

			k_delete(rowptr);

			//
			jpeg_finish_decompress(&jptr);
			jpeg_destroy_decompress(&jptr);

			// 32비트로 강제 변환함
			if (K_OFLAG(flags, QGIMGF_BGRA))
			{
				img->Initialize(width, height, QGCF32_BGRA);
				buf = (kbyte*)img->GetData();

				if (!cmyk)
				{
					tmp = 3 * width * height;

					for (i = z = 0; i < tmp; i += 3, z += 4)
					{
						buf[z + 3] = 0xFF;
						buf[z + 2] = output[i + 2];
						buf[z + 1] = output[i + 1];
						buf[z + 0] = output[i + 0];
					}
				}
				else
				{
					tmp = 4 * width * height;

					for (i = 0; i < tmp; i += 4)
					{
						float f = output[i + 3] / 255.0f;
						buf[i + 3] = 0xFF;
						buf[i + 2] = (kbyte)(output[i + 2] * f);
						buf[i + 1] = (kbyte)(output[i + 1] * f);
						buf[i + 0] = (kbyte)(output[i + 0] * f);
					}
				}

			}
			else
			{
				img->Initialize(width, height, QGCF32_RGBA);
				buf = (kbyte*)img->GetData();

				if (!cmyk)
				{
					tmp = 3 * width * height;

					for (i = z = 0; i < tmp; i += 3, z += 4)
					{
						buf[z + 3] = 0xFF;
						buf[z + 2] = output[i + 0];
						buf[z + 1] = output[i + 1];
						buf[z + 0] = output[i + 2];
					}
				}
				else
				{
					tmp = 4 * width * height;

					for (i = z = 0; i < tmp; i += 3, z += 4)
					{
						float f = output[z + 3] / 255.0f;
						buf[i + 3] = 0xFF;
						buf[i + 2] = (kbyte)(output[z + 0] * f);
						buf[i + 1] = (kbyte)(output[z + 1] * f);
						buf[i + 0] = (kbyte)(output[z + 2] * f);
					}
				}
			}

			k_delete(output);

			return true;
		}
	};

	class ImageJpegWriter : public qgWriter, public tpl::MemBase
	{
	public:
		struct MemMgr
		{
			jpeg_destination_mgr pub;
			kStream* st;
			JOCTET buffer[BUFSIZE];
		};

	public:
		K_OVR void Dispose()
		{
			delete this;
		}

		K_OVR bool Test(const char* filename)
		{
			return k_strieqlv(filename, "jpg", "jpeg", NULL) > 0;
		}

		K_OVR bool Write(kStream* st, kObj* ctnr, kint flags)
		{
			qgImage* img = (qgImage*)ctnr;
			const qgPixelProp& prop = img->GetPixelProp();

			if (prop.fmt != QGCF32_RGB && prop.fmt != QGCF32_RGBA &&
				prop.fmt != QGCF32_BGR && prop.fmt != QGCF32_BGRA)
				return false;

			kint width = img->GetWidth();
			kint height = img->GetHeight();
			kint outpitch = width * 3;	// 24비트 RGB = 3바이트

			//
			kint quality = 80;
			jpeg_compress_struct jptr;
			jpeg_error_mgr jerr;

			jptr.err = jpeg_std_error(&jerr);

			jpeg_create_compress(&jptr);
			CreatedDestination(&jptr, st);

			jptr.image_width = width;
			jptr.image_height = height;
			jptr.input_components = 3;
			jptr.in_color_space = JCS_RGB;

			jpeg_set_defaults(&jptr);

			jpeg_set_quality(&jptr, quality, true);
			jpeg_start_compress(&jptr, true);

			//
			kuint* data = (kuint*)img->GetData();
			kbyte* line = k_new(outpitch, kbyte);
			JSAMPROW rowp[1];
			rowp[0] = line;

			if (prop.flags & QGCFF_BGR)
			{
				while (jptr.next_scanline < jptr.image_height)
				{
					kint x, t;
					for (t = x = 0; x < width; x++, t += 3)
					{
						kvint& v = (kvint&)data[x];
						line[t + 0] = v.b.a;
						line[t + 1] = v.b.b;
						line[t + 2] = v.b.c;
					}

					data += width;
					jpeg_write_scanlines(&jptr, rowp, 1);
				}
			}
			else
			{
				while (jptr.next_scanline < jptr.image_height)
				{
					kint x, t;
					for (t = x = 0; x < width; x++, t += 3)
					{
						kvint& v = (kvint&)data[x];
						line[t + 0] = v.b.c;
						line[t + 1] = v.b.b;
						line[t + 2] = v.b.a;
					}

					data += width;
					jpeg_write_scanlines(&jptr, rowp, 1);
				}
			}

			k_delete(line);

			jpeg_finish_compress(&jptr);
			jpeg_destroy_compress(&jptr);

			return true;
		}

		static void CreatedDestination(j_compress_ptr ptr, kStream* st)
		{
			if (!ptr->dest)
				ptr->dest = (jpeg_destination_mgr*)(*ptr->mem->alloc_small)((j_common_ptr)ptr, JPOOL_PERMANENT, sizeof(MemMgr));

			MemMgr* dest = (MemMgr*)ptr->dest;

			dest->pub.init_destination = _InitDestination;
			dest->pub.empty_output_buffer = _EmptyOutputBuffer;
			dest->pub.term_destination = _TermDestination;
			dest->st = st;
		}

		static void _InitDestination(j_compress_ptr ptr)
		{
			MemMgr* dest = (MemMgr*)ptr->dest;
			dest->pub.next_output_byte = dest->buffer;
			dest->pub.free_in_buffer = BUFSIZE;
		}

		static boolean _EmptyOutputBuffer(j_compress_ptr ptr)
		{
			MemMgr* dest = (MemMgr*)ptr->dest;
			if (dest->st->Write(dest->buffer, 0, BUFSIZE) != BUFSIZE)
				ERREXIT(ptr, JERR_FILE_WRITE);
			dest->pub.next_output_byte = dest->buffer;
			dest->pub.free_in_buffer = BUFSIZE;
			return true;
		}

		static void _TermDestination(j_compress_ptr ptr)
		{
			MemMgr* dest = (MemMgr*)ptr->dest;
			kint count = (kint)(BUFSIZE - dest->pub.free_in_buffer);
			if (dest->st->Write(dest->buffer, 0, count) != count)
				ERREXIT(ptr, JERR_FILE_WRITE);
		}
	};
}

namespace _img
{
	qgReader* CreateImageJpegLoader()
	{
		return new ImageJpegLoader();
	}

	qgWriter* CreateImageJpegWriter()
	{
		return new ImageJpegWriter();
	}
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif
